package cn.edu.tsinghua.card;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import com.github.houbb.pinyin.bs.PinyinBs;
import com.github.houbb.pinyin.constant.enums.PinyinStyleEnum;
import com.github.houbb.pinyin.spi.IPinyinToneStyle;
import com.github.houbb.pinyin.support.style.PinyinToneStyles;

/**
 * 实现汉字姓名的拼音拼写检查的工具类。
 */
public class NamePinyinChecker {
	private static final PinyinBs PINYIN;
	
	static {
		// 初始化PinyinBs示例，选择无声调的正常模式。
		IPinyinToneStyle pinyinTone = PinyinToneStyles.getTone(PinyinStyleEnum.NORMAL);
		PINYIN = PinyinBs.newInstance().style(pinyinTone).init();
		// 调用一次转换，完成PinyinBs中的延迟加载工作。
		PINYIN.toPinyin("");
	}

	private NamePinyinChecker() {
	}

	/**
	 * 汉字姓名的格式规则
	 */
	public static final Pattern CHINESE_NAME_PATTERN = Pattern.compile(
			"^[\\u4e00-\\u9fcb\\u3400-\\u4db5\\x{20000}-\\x{2B81D}]+(\\u00b7[\\u4e00-\\u9fcb\\u3400-\\u4db5\\x{20000}-\\x{2B81D}]+)*+$");
	/**
	 * 拼音拼写的格式规则
	 */
	public static final Pattern ENGLISH_NAME_PATTERN = Pattern.compile("^([A-Z][a-z]*)( [A-Z][a-z]*)*+$");

	/**
	 * 比对正确
	 */
	public static final int NORMAL = 0;
	/**
	 * 汉字姓名格式不正确
	 */
	public static final int INVALID_CHINESE_NAME = 1;
	/**
	 * 拼音拼写格式不正确
	 */
	public static final int INVALID_ENGLISH_NAME = 2;
	/**
	 * 汉字姓名和拼音拼写无法对应
	 */
	public static final int UNMATCHED_PINYIN = 3;

	protected static final Pattern YUE = Pattern.compile("üe", Pattern.CANON_EQ);
	protected static final Pattern YU = Pattern.compile("ü", Pattern.CANON_EQ);
	protected static final Pattern SPACE = Pattern.compile(" ");
	protected static final Pattern DOT = Pattern.compile("\u00b7");

	/**
	 * 将包含ü的拼音转化为国家移民管理局规定的拼写
	 * @param input 包含ü的拼音拼写 
	 * @return 不包含ü的规定拼写
	 */
	protected static String convertYu(String input) {
		if (input.indexOf('ü') >= 0) {
			input = YU.matcher(YUE.matcher(input).replaceAll("ue")).replaceAll("yu");
		}
		return input;
	}

	/**
	 * 列出汉语姓名的全部可能的拼音拼写组合，采用全小写无分隔格式，用于比对。
	 * @param cnname 汉语姓名
	 * @return 拼音拼写组合
	 */
	protected static List<String> listCandidates(String cnname) {
		List<String> start = new ArrayList<>();
		for (char ch : cnname.toCharArray()) {
			if (start.isEmpty()) {
				for (String p : PINYIN.toPinyinList(ch)) {
					start.add(convertYu(p));
				}
			} else {
				List<String> next = new ArrayList<>();
				for (String p : PINYIN.toPinyinList(ch)) {
					p = convertYu(p);
					for (String s : start) {
						next.add(s + p);
					}
				}
				start = next;
			}
		}
		return start;
	}

	protected static String indicatePinyin(String cnname) {
		StringBuilder sb = new StringBuilder();
		for (char ch : cnname.toCharArray()) {
			sb.append(String.join("/", Set.copyOf(PINYIN.toPinyinList(ch))));
			sb.append(" ");
		}
		sb.deleteCharAt(sb.length() - 1);
		return sb.toString();
	}

	/**
	 * 核对汉字姓名和拼音拼写是否一致
	 * @param cnname 汉字姓名
	 * @param enname 拼音拼写
	 * @return
	 */
	public static Map.Entry<Integer, String> check(String cnname, String enname) {
		if (cnname == null || cnname.isEmpty()) {
			return Map.entry(INVALID_CHINESE_NAME, "汉字姓名为空");
		}

		if (!CHINESE_NAME_PATTERN.matcher(cnname).matches()) {
			return Map.entry(INVALID_CHINESE_NAME, String.format("汉字姓名\"%s\"格式错误，不符合连续汉字字符（可以以点“·”分隔）的要求", cnname));
		}
		if (!ENGLISH_NAME_PATTERN.matcher(enname).matches()) {
			return Map.entry(INVALID_ENGLISH_NAME, String.format("拼音拼写\"%s\"格式错误，不符合仅含英文字母、以单空格分隔且仅首字母大写的要求", enname));
		}

		String loe = SPACE.matcher(enname).replaceAll("").toLowerCase();
		List<String> candidates = listCandidates(DOT.matcher(cnname).replaceAll(""));
		if (!candidates.contains(loe)) {
			return Map.entry(UNMATCHED_PINYIN,
					String.format("拼音拼写\"%s\"与汉字姓名\"%s\"拼音（%s）不匹配", enname, cnname, indicatePinyin(cnname)));
		}

		if (cnname.indexOf('\u00b7') >= 0) { // 少数民族姓名中包含有·的情况要处理分隔问题
			String[] cnparts = DOT.split(cnname);
			String[] enparts = SPACE.split(enname.toLowerCase());
			if (cnparts.length != enparts.length) {
				return Map.entry(UNMATCHED_PINYIN, String.format("少数民族汉字姓名\"%s\"与拼音\"%s\"分隔不匹配", enname, cnname));
			}

			for (int i = 0; i < cnparts.length; i++) {
				candidates = listCandidates(cnparts[i]);
				if (!candidates.contains(enparts[i])) {
					return Map.entry(UNMATCHED_PINYIN, String.format("少数民族汉字姓名\"%s\"与拼音\"%s\"分隔不匹配", enname, cnname));
				}
			}
		}

		return Map.entry(0, "");
	}
	
	/**
	 * 生成汉字姓名的最常用读音的拼音拼写，处理了常见复姓和少数民族姓名情况。
	 * @param cnname 汉字姓名
	 * @return 最常用读音的拼音拼写。
	 */
	public static String getPinyinSpelling(String cnname) {
		if(!CHINESE_NAME_PATTERN.matcher(cnname).matches()) {
			throw new IllegalArgumentException(String.format("汉字姓名\"%s\"格式错误，不符合连续汉字字符（可以以点“·”分隔）的要求", cnname));
		}
		
		if (cnname.indexOf('\u00b7') < 0) {
			String surname = null;
			String givenName = null;
			for(String cpsn : CompositeSurnames.getSurnames()) {
				if(cnname.startsWith(cpsn)) {
					surname = cpsn;
					givenName = cnname.substring(cpsn.length());
					break;
				}
			}
			if(surname == null) {
				surname = cnname.substring(0, 1);
				givenName = cnname.substring(1);
			}
			StringBuilder sb = new StringBuilder();
			
			String sp = SPACE.matcher(PINYIN.toPinyin(surname)).replaceAll("");
			sb.append(sp.substring(0, 1).toUpperCase()).append(sp.substring(1));			
			
			if(givenName.length() > 0) {
				String gp =  SPACE.matcher(PINYIN.toPinyin(givenName)).replaceAll("");
				sb.append(" ").append(gp.substring(0, 1).toUpperCase()).append(gp.substring(1)).append(" ");
			}
			return convertYu(sb.toString());			
		}
		else {
			StringBuilder sb = new StringBuilder();
			for(String cnpart : DOT.split(cnname)) {
				if(cnpart.length() == 0) {
					continue;
				}
				
				String p = SPACE.matcher(PINYIN.toPinyin(cnpart)).replaceAll("");
				sb.append(p.substring(0, 1).toUpperCase()).append(p.substring(1)).append(" ");
			}
			
			return convertYu(sb.toString().strip());			
		}
	}

}
